#region Using References

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Beaker.Services.FileSystem;

#endregion

namespace Beaker.OpenCube.Specs.Builders
{
	[ExcludeFromCodeCoverage]
	public class FileSystemServiceBuilder
		: Builder<IFileSystemService>
	{
		private readonly FileSystemService _fileSystemService = new FileSystemService();

		public FileSystemServiceBuilder WithFile( string fullPath, byte[] content )
		{
			Folder folder = _fileSystemService;

			string[] segments = fullPath.Split( new[] { '/', '\\' } );
			for( int i = 0; i < segments.Length - 1; i++ )
				folder = folder.AddOrGetFolder( segments[i] );
			folder.AddFile( segments.Last(), content );

			return this;
		}

		public FileSystemServiceBuilder WithFile( string fullPath, Stream content )
		{
			var pos = content.Position;
			var memory = new MemoryStream();
			content.CopyTo( memory );
			memory.Flush();
			memory.Position = 0;
			content.Position = pos;

			WithFile( fullPath, memory.ToArray() );

			return this;
		}

		public FileSystemServiceBuilder WithFile( string fullPath )
		{
			WithFile( fullPath, new byte[0] );
			return this;
		}

		public FileSystemServiceBuilder WithFile( string folderPath, string fileName, byte[] content )
		{
			WithFile( Path.Combine( folderPath, fileName ), content );
			return this;
		}

		public FileSystemServiceBuilder WithFile( string folderPath, string fileName, Stream content )
		{
			WithFile( Path.Combine( folderPath, fileName ), content );
			return this;
		}

		public FileSystemServiceBuilder WithFile( string folderPath, string fileName )
		{
			WithFile( Path.Combine( folderPath, fileName ), new byte[0] );
			return this;
		}

		public FileSystemServiceBuilder WithFolder( string fullPath )
		{
			Folder folder = _fileSystemService;

			string[] segments = fullPath.Split( new[] { '/', '\\' } );
			for( int i = 0; i < segments.Length; i++ )
				folder = folder.AddOrGetFolder( segments[i] );

			return this;
		}


		protected override IFileSystemService OnBuild()
		{
			return _fileSystemService;
		}


		private class File
			: IFile
		{
			private readonly Folder _parent;
			private readonly string _name;
			private readonly byte[] _content;

			public File( Folder parent, string name, byte[] content )
			{
				_parent = parent;
				_name = name;
				_content = content;
			}

			/// <summary>
			/// Gets the name of the entry
			/// </summary>
			public string Name
			{
				get { return _name; }
			}

			/// <summary>
			/// Get the folder this entry is in
			/// </summary>
			public IFolder ParentFolder
			{
				get { return _parent; }
			}

			/// <summary>
			/// Open the file for reading
			/// </summary>
			/// <returns>A stream of the content of the file</returns>
			public Stream OpenRead()
			{
				return new MemoryStream( _content );
			}

			/// <summary>
			/// Open the text-file for reading
			/// </summary>
			/// <returns>A text-reader with the content of the file</returns>
			public TextReader OpenText()
			{
				return
					new StreamReader(
						OpenRead()
					);
			}

			public override string ToString()
			{
				if( _parent != null )
				{
					string parentPath = _parent.ToString();
					if( !string.IsNullOrEmpty( parentPath ) )
						return Path.Combine( _parent.ToString(), _name );
				}

				return _name;
			}
		}

		private class Folder
			: IFolder
		{
			private readonly Folder _parent;
			private readonly string _name;
			private readonly Collection<Folder> _folders = new Collection<Folder>();
			private readonly Collection<File> _files = new Collection<File>();

			public Folder( Folder parent, string name )
			{
				_parent = parent;
				_name = name;
			}

			public Folder AddOrGetFolder( string name )
			{
				Folder folder = _folders.FirstOrDefault( f => f.Name.Equals( name, StringComparison.InvariantCultureIgnoreCase ) );
				if( folder == null )
				{
					folder = new Folder( this, name );
					_folders.Add( folder );
				}
				return folder;
			}

			public void AddFile( string name, byte[] content )
			{
				_files.Add(
					new File( this, name, content )
				);
			}

			/// <summary>
			/// Gets the name of the entry
			/// </summary>
			public string Name
			{
				get { return _name; }
			}

			/// <summary>
			/// Get the folder this entry is in
			/// </summary>
			public IFolder ParentFolder
			{
				get { return _parent; }
			}

			/// <summary>
			/// Gets the files in this folder.
			/// </summary>
			/// <returns>
			/// A sequence of files.
			/// </returns>
			public IEnumerable<IFile> GetFiles()
			{
				return _files;
			}

			/// <summary>
			/// Gets the file with the given name.
			/// </summary>
			/// <param name="fileName">The name of the file to get.</param>
			/// <returns>The file with the given name, or <c>null</c> when it could not be found.</returns>
			public IFile GetFile( string fileName )
			{
				return _files.FirstOrDefault( f => f.Name.Equals( fileName, StringComparison.InvariantCultureIgnoreCase ) );
			}

			/// <summary>
			/// Gets the folders in this folder.
			/// </summary>
			/// <returns>
			/// A sequence of folders.
			/// </returns>
			public IEnumerable<IFolder> GetFolders()
			{
				return _folders;
			}

			/// <summary>
			/// Gets the folder with the given name.
			/// </summary>
			/// <param name="folderName">The name of the folder to get.</param>
			/// <returns>The folder with the given name, or <c>null</c> when it could not be found.</returns>
			public IFolder GetFolder( string folderName )
			{
				return _folders.FirstOrDefault( f => f.Name.Equals( folderName, StringComparison.InvariantCultureIgnoreCase ) );
			}

			/// <summary>
			/// Gets the entires in this folder.
			/// </summary>
			/// <param name="option">The options for searching for items.</param>
			/// <returns>
			/// A sequence of items.
			/// </returns>
			public IEnumerable<IFileSystemEntry> GetEntries()
			{
				return _folders.Cast<IFileSystemEntry>().Concat( _files );
			}

			/// <summary>
			/// Gets the entry with the given name in this folder.
			/// </summary>
			/// <param name="entryName">Name of the entry to get.</param>
			/// <returns>The entry with the given name, or <c>null</c> when it could not be found.</returns>
			public IFileSystemEntry GetEntry( string entryName )
			{
				return GetEntries().FirstOrDefault( f => f.Name.Equals( entryName, StringComparison.InvariantCultureIgnoreCase ) );
			}

			public override string ToString()
			{
				if( _parent != null )
				{
					string parentPath = _parent.ToString();
					if( !string.IsNullOrEmpty( parentPath ) )
						return Path.Combine( _parent.ToString(), _name );
				}

				return _name;
			}
		}

		private class FileSystemService
			: Folder, IFileSystemService
		{
			public FileSystemService()
				: base( null, null )
			{
			}

			/// <summary>
			/// Parse the given path and return a file or folder for it
			/// </summary>
			/// <param name="path">The path to parse</param>
			/// <returns>A IFileSystemEntry with the file or folder, or <c>null</c> when the file or folder does not exist</returns>
			/// <exception cref="FormatException">Thrown when the path could not be parsed.</exception>
			public IFileSystemEntry ParsePath( string path )
			{
				IFolder folder = this;
				IFileSystemEntry entry = null;
				string[] segments = path.Split( new[] { '/', '\\' } );
				foreach( string entryName in segments )
				{
					if( folder == null )
						throw new DirectoryNotFoundException();

					entry = folder.GetEntry( entryName );
					folder = entry as IFolder;
				}

				return entry;
			}

			/// <summary>
			/// Gets the requested special folder
			/// </summary>
			/// <param name="folder">The special folder to get</param>
			/// <returns>The requested special folder</returns>
			public IFolder GetSpecialFolder( Environment.SpecialFolder specialFolder )
			{
				string fullPath = Environment.GetFolderPath( specialFolder );

				Folder folder = this;
				string[] segments = fullPath.Split( new[] { '/', '\\' } );
				foreach( string folderName in segments )
					folder = folder.AddOrGetFolder( folderName );

				return folder;
			}
		}
	}
}